ユニットテストにまつわる10の勘違い
渡辺です。さる方面からテスト系のエントリーがまだか…と催促されたので、ユニットテストについて少し考えてみたいと思います。
最近、TwitterのTLをチェックしていると、JUnitを利用しているにも関わらず違和感のあるTweetや、原因をJUnitにして本来解決すべき問題から目をそらしているようなTweetを多く見かけます。そこで、JUnitをによるユニットテストに関するありがちな勘違いをまとめてみました。
なお、JUnitの部分は、RSpecでもNUnitでも適当に置き換えて読んでも構いません。
1.JUnitを使うことが目的という勘違い
JUnitを利用すること自体を目的にしたところで何も得る事はありません。
ありがちな話ですが、「納品物としてJUnitのテストコード(または実行結果)を求められている」ことが理由でJUnitを利用しているならば、それは足かせでしかない可能性があります。やらない方がマシかもしれません。
JUnitはユニットテストを効率よく書くためのテスティングフレームワーク(ツール)です。ユニットテストとは、クラスやメソッドを対象とした最も小さな粒度の開発者が行うテストです。したがって、ユニットテストが行いやすいクラス設計が必要不可欠です。また、同じプロダクトの中でも、適用しやすい部分もあれば、適用しにくい部分もあります。それらを踏まえた上で、効率よくユニットテストを行うためにJUnitを利用すべきです。
2.ユニットテストは誰でもできるという勘違い
ユニットテストを実践するにはプログラミングだけでなくソフトウェアテストのスキルと経験も必要です。
JUnitで行うユニットテストは、ソフトウェアテストのひとつです。ソフトウェアテストには、同値分析や境界値分析など多くの手法があります。それらは複雑で難しいソフトウェアのテストという問題を解決するために考案されてきた先人の知恵です。それらを学ばずにJUnitのテストコードを書いたとしても、効果的なテストとならないのは当然です。
また、ユニットテストを行うのは原則としてプログラマです。専業のテストエンジニアのような専門的知識やスキルまでは必要ないでしょう。しかし、基礎的な知識やスキルはあってしかるべきです。WEBアプリケーションを作るプログラマがHTMLやCSSについても、最低限の知識やスキルが必要なことと変わりません。
なにより、ソフトウェアテストの基礎知識がなければ、どの程度テストコードを書けば必要十分かという判断をすることができません。さらにテストコードを書くためのプログラミングスキルも必要です。
テストコードを書くには、プロダクションコードを書くよりもスキルと経験が必要です。
3.テスト対象が完璧な設計であるという勘違い
ユニットテストを実践することによりプロダクションコードが適切に修正されていく状態でなければなりません。
ユニットテストの重要な目的のひとつに「テスト対象クラスやメソッドの使用感を把握する」ことがあります。机上の設計やフィーリングだけで書いたコードは、使ってみての違和感や使いにくさに気付き難いものです。それらの使用感は、実際に利用してみてはじめて気付きます。だから、テストコードを書くことで実際に利用してみます。これはサンプルコードを書く事に他なりません。
サンプルコードを書くと、「これは使いにくい」と感じたり、「これはこうした方が使いやすい」と感じたりします。それはユニットテストの大きな効果です。もし、使いにくいと感じたならば、すぐにテスト対象を修正します。テスト対象が完璧な設計で変更する余地が全く無い、または変更してはならないのでは、ユニットテストを行ってもストレスしか溜まらないでしょう。
なお、テスト駆動開発(TDD: Test Driven Development)で推奨されているテストファーストを実践することで、「テスト対象クラスやメソッドの使用感を把握する」効果は最大となります。なぜならば、先に使用感を検証してからプロダクションコードを書くため、「使いにくい」と感じた場合の修正が大幅に減るからです。
4.ユニットテストはコストが低いという勘違い
ユニットテストを導入するならば、実装コストの2倍以上を見積もるべきです。
プロジェクトやテスト方針にも依存しますが、プロダクションコードとテストコードの比率は数倍から10倍程度になります。少なくとも、プロダクションコードよりも多くなるでしょう。様々な入力値に対応したテストケースを書くという性質的に、似たようなコードが多くなります。
また、似たようなコードが多くなるため、コードをDRYにしていく工夫が必要ですが、テストケースとしての可読性を損なってはメンテナンス性に問題が生じます。このため、そのプロダクト用のテスト用ユーティリティクラスなどを作っていかなければなりません。これらもユニットテストのコストに含まれます。
なお、テストコードを書くときは、テストにおけるパターンを網羅するために様々な状況やテストデータを検討します。これは、非常に地味な作業です。プロダクションコードは一瞬で書けるかもしれませんが、テストコードを書くためにはより多くの状況を想定しなければなりません。これは非常に時間がかかります。
ユニットテストを書くにはコード量もさることながら、多くの時間を使います。感覚として、自分の見積もりでは2倍から5倍程度はかかると思います。そのコストを払う価値と覚悟がなければユニットテストを行うのは危険です。
5.ユニットテストを行うことで品質があがるという勘違い
適切なユニットテストが行われていない限り品質はあがりません。
ユニットテストはプログラマが書いたコードが、プログラマが期待された振る舞いをするかどうかを検証するためのプログラムです。もし、根本的に要件定義が間違っていたならば、顧客が満足するプロダクトはリリースできません。基本的な設計が間違っていれば、ユニットテストで検証する振る舞い自体が間違っていることになります。プログラマが、テスト対象に期待する振る舞いをイメージできなければ検証する振る舞いも解るわけがありません。バグだらけのコードしか書けないプログラマが書いたテストコードはバグだらけです。
品質をあげるための、ただひとつな方法は、優秀な開発者を採用することです。
6.プロダクトのすべてのコードにユニットテストができるという勘違い
プロダクトが採用しているアーキテクチャによっては、ユニットテストが難しいレイヤーや、ユニットテストが効果的でないレイヤーも存在します。
例えば、Webの3層アーキテクチャ(プレゼンテーション層/サービス層/パーシステンス層)では、プレゼンテーション層はユニットテストが困難で効果的でない代表的なレイヤーです。プレゼンテーション層はHTMLなどでレンダリングされるため検証する方法が難しく、検証するコストをかけたところで変更が多い個所なのでメンテナンスコストを払いきれなくなるでしょう。また、サービス層やパーシステンス層に比べると相対的に重要度も低いため、「プレゼンテーション層に対して、ユニットテストは行わない」というテスト方針は珍しくありません。プロジェクトのコストは有限ですから、効果的なテスト設計が必要です。
勿論、ユニットテストを行わない部分については、別のテストなどでカバーしてください。例えば、「プレゼンテーション層のユニットテストを行わない代わりに自動化したインテグレーションテスト(結合テスト)を行う」といった方針です。
ユニットテストも、プロジェクトやプロダクトの採用しているアーキテクチャに合わせて、柔軟に適用していかなければなりません。
7.カバレッジが品質や進捗の指標という勘違い
カバレッジは効率よくユニットテストを行うためのツールでしかありません。
多くの組織でカバレッジという麻薬の中毒者がいると思われます。特にプログラミングを知らないようなマネージャ層にとって、100%とか90%という具体的な数字は心地よく聞こえます。しかし、それはユニットテストがプロダクションコードのどの程度の割合をカバーしているかを表す指標でしかありません。
極端な話ですが、ある機能がまるまる実装漏れである場合よりも、実装したがユニットテストがない場合の方が、カバレッジは低くなります。プロダクトのコードのかなりの割合はエラー処理ですから、エラー処理が抜けているほどカバレッジは高くなる可能性があります。エラー処理を書くと対応するテストコードも書かなければならないため、気がつかなかったことにして実装しない不届き者が現れるかもしれません。
8.テストコードを書けば充分という勘違い
ユニットテストは、繰り返し何度も実行することではじめて効果があります。
テストコードを書いたならばすぐに実行しますが、すぐに全てのテストコードを実行するのは面倒になります。このため、裏側でJenkinsなどの継続的インテグレーションツールを利用して、自動的にテストを実行出来る環境を構築しておかなければなりません。そうすることで、プロダクトに発生した問題を早期に発見する事ができます。問題が早期に発見できれば、すぐに修正する事ができるでしょう。
9.ユニットテストさえすればインテグレーションテストをしなくて良いという勘違い
ユニットテストは、開発を支援するデベロッパーテストであり、要求を満たしているかを検証するためのテストではありません。
ユニットテストは、クラスやメソッドというプログラムの最小単位を対象とし、プログラマが行うテストです。プロダクトはそれらのクラスやメソッドを組み合わせる事で構築されるため、クラスやメソッドが期待される振る舞いをするかどうかを検証することは大切です。しかし、どれだけ個々のクラスやメソッドがユニットテストされ、期待される振る舞いが保証されていても、エンドトゥエンドでプロダクトを利用した時に正しく動作する保証はありません。逆に、個々のクラスやメソッドには大きな不具合があったとしても、エンドトゥエンドで利用した時に、結果的に期待される振る舞いをしていたとしたならば、顧客にとっては有益なプロダクトとなります。したがって、どれだけユニットテストを行ったとしても、インテグレーションテスト(結合テスト)を行わない理由とはなりません。
10.どんなプロジェクトにもユニットテストは有効だという勘違い
ユニットテストとなんだろうと、銀の弾丸となるツール・開発手法・開発プロセスはありません。適用して効果的なプロジェクトもあれば、逆効果となるプロジェクトもあります。本当にユニットテストが効果的となるプロジェクトであるかは検討した上で導入すべきです。
ユニットテストが効果的なプロダクトとは、開発チームが固定または大きく変動せず、長期にわたってメンテナンスが行われ、比較的に頻繁なリリースが行われるプロダクトです。これらの要素を満たしていないほどユニットテストの効果は小さくなり、費用対効果を考えた上で導入するか否かを検討すべきです。
例えば、開発チームがあまりにも変動的すぎると、テストコードのメンテナンスコストが高くなります。プロダクションコードだけでも追うのが大変な状況で、テストコードも同じように追っていかなくてはならないのですから当然です。テストコードが読みやすく整理されていることも必要です。いきなり渡された数千件にも渡るテストケースをメンテできるはずがありません。
プロトタイプのように、短期間で寿命を全うするようなプロダクトではユニットテストの効果は小さくなります。なぜならばユニットテストは自動化され、何度でも実行するからこそ、テストコードを書くコストが回収できるからです。1回だけテストを行うだけならば手動テストの方が効率がよい場合もあります。
リリースが1年に1回のように長期スパンである場合も同様です。手動テストのコストが相対的に小さくなるため、テストを自動化するメリットが小さくなります。逆に2週間に1回など頻繁なリリースを行う場合、手動テストだけでは太刀打ちできなくなるでしょう。個人的な目安としては3ヶ月に1回程度のリリースがないと大きなメリットとはならないと感じます。
最後に
まとめます。
- JUnitは効率よくテストを行うためのツールであり、使うことが目的としてはならない
- ユニットテストを行うにはプログラミングだけではなくソフトウェアテストのスキルも必要である
- ユニットテストを行いながら、テスト対象を改善していく体制が必要である
- ユニットテストのコストは非常に高い
- 優秀な開発者がユニットテストを行わなければ品質は高くならない
- ユニットテストが適用しにくい部分も存在する
- カバレッジはユニットテストを行う時の指標でしかない
- ユニットテストは繰り返し何度も実行することで効果がある
- ユニットテストだけでは不十分なので、インテグレーションテストも必要
- そのプロジェクトでユニットテストの効果が充分にあるかを検討すべき
みなさんの開発チームでは、ユニットテストしてますか?